package com.white.camlib;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.util.Log;
import android.util.Pair;
import android.view.Surface;
import android.view.SurfaceHolder;

import com.white.loglib.L;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutorService;

/**
 * @author ytf
 * Created by on 2021/04/24 10:21
 */
public class CameraKit
{

    public static Camera openCamera(boolean frontOrBack)
    {
        int id = getCameraId(frontOrBack);
        if (id != -1)
        {
            return Camera.open(id);
        }
        return null;
    }

    public static int getCameraId(boolean frontOrBack)
    {
        int count = Camera.getNumberOfCameras();
        for (int i = 0; i < count; i++)
        {
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(i, info);
            if (frontOrBack)
            {
                if (Camera.CameraInfo.CAMERA_FACING_FRONT == info.facing)
                {
                    return i;
                }
            } else
            {
                if (Camera.CameraInfo.CAMERA_FACING_BACK == info.facing)
                {
                    return i;
                }
            }
        }
        return -1;
    }

    public static void startSurfacePreview(Camera camera, SurfaceHolder holder)
    {
        if (camera == null || holder == null) return;

        try
        {
            camera.setPreviewDisplay(holder);
            camera.startPreview();
        } catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    public static void startTexurePreview(Camera camera, SurfaceTexture surface)
    {
        if (camera == null || surface == null) return;
        try
        {
            camera.setPreviewTexture(surface);
            camera.startPreview();

        } catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    public static void registerFrameCallback(Camera camera,
                                             final int previewWidth,
                                             final int previewHeight,
                                             final int format,
                                             final FrameCallback callback){
        int bufSize = previewWidth * previewHeight * ImageFormat.getBitsPerPixel(format) / 8;
        camera.addCallbackBuffer(new byte[bufSize]);
        camera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
            @Override
            public void onPreviewFrame(byte[] data, Camera camera) {
                camera.addCallbackBuffer(data);
                callback.onCameraFrame(data,previewWidth, previewHeight, format);
            }
        });
    }

    public static void unregisterFrameCallback(Camera camera){
        if(camera == null) return;

        camera.addCallbackBuffer(null);
        camera.setPreviewCallbackWithBuffer(null);
    }

    public static void releaseCamera(Camera camera)
    {
        if (camera == null) return;
        try
        {
            stopPreview(camera);
            camera.release();
        } catch (Exception e)
        {

        }
    }

    public static void stopPreview(Camera camera)
    {
        if (camera == null) return;
        try
        {
            camera.stopPreview();
        } catch (Exception e)
        {
        }
    }

    public static Pair<Camera.Size, Camera.Size> setupBaseParams(int viewWidth, int viewHeight, Camera camera)
    {

        if (camera == null) return null;

        Camera.Parameters parameters = camera.getParameters();
        List<String> supports = parameters.getSupportedFocusModes();
        if (supports.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE))
        {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        } else if (supports.contains(Camera.Parameters.FOCUS_MODE_AUTO))
        {
            //自动对焦(单次)
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }
        Camera.Size previewSize = getBestPreviewSize(viewWidth, viewHeight, camera);
        parameters.setPreviewSize(previewSize.width, previewSize.height);
        Camera.Size pictureSize = getBestPictureSize(previewSize.width, previewSize.height, camera);
        Log.e("s", "select preview size [" + previewSize.width + "," + previewSize.height + "], " +
                "pic size [" + pictureSize.width + "," + pictureSize.height + "]");
        parameters.setPictureSize(pictureSize.width, pictureSize.height);
        //设置格式,所有的相机都支持 NV21格式
        parameters.setPreviewFormat(ImageFormat.NV21);
        camera.setParameters(parameters);
        return new Pair<>(previewSize, pictureSize);
    }

    public static Camera.Size getBestPreviewSize(final int viewWidth, final int viewHeight, Camera camera)
    {

        final float vRatio = simple2(Math.max(viewWidth, viewHeight) * 1.0f / Math.min(viewWidth, viewHeight));
        List<Camera.Size> ss = camera.getParameters().getSupportedPreviewSizes();
        List<Camera.Size> sampleRatios = new ArrayList<>();
        List<Pair<Float, Camera.Size>> otherRatios = new ArrayList<>();
        for (int i = 0; i < ss.size(); i++)
        {
            //获取到的size width > height
            Camera.Size size = ss.get(i);
            Log.e("s", i + " preview, width = " + size.width + " height = " + size.height);
            if (viewWidth == size.height && viewHeight == size.width)
            {
                return size;
            }
            float cr = simple2(size.width * 1.0f / size.height);
            if (cr == vRatio)
            {
                sampleRatios.add(size);
            } else
            {
                otherRatios.add(new Pair<>(cr, size));
            }
        }
        if (sampleRatios.size() > 0)
        {
            List<Pair<Integer, Integer>> map = new ArrayList<>();
            for (int i = 0; i < sampleRatios.size(); i++)
            {
                Camera.Size size = sampleRatios.get(i);
                int delta = size.height - viewHeight;
                if (delta >= 0)
                {
                    map.add(new Pair<>(i, delta));
                }
            }
            Collections.sort(map, new Comparator<Pair<Integer, Integer>>()
            {
                @Override
                public int compare(Pair<Integer, Integer> o1, Pair<Integer, Integer> o2)
                {
                    int r1 = o1.second;
                    int r2 = o2.second;
                    return Integer.compare(r1, r2);
                }
            });
            Pair<Integer, Integer> first = map.get(0);
            return sampleRatios.get(first.first);
        }

        Collections.sort(otherRatios, new Comparator<Pair<Float, Camera.Size>>()
        {
            @Override
            public int compare(Pair<Float, Camera.Size> o1, Pair<Float, Camera.Size> o2)
            {
                float r1 = o1.first;
                float r2 = o2.first;
                float ret = Math.abs(r1 - vRatio) - Math.abs(r2 - vRatio);
                return ret > 0 ? 1 : ret < 0 ? -1 : 0;
            }
        });

        return otherRatios.get(0).second;
    }

    public static Camera.Size getBestPictureSize(final int previewWidth, final int previewHeight, Camera camera)
    {
        final float pRatio = simple2(Math.max(previewWidth, previewHeight) * 1.0f / Math.min(previewWidth, previewHeight));
        List<Camera.Size> ss = camera.getParameters().getSupportedPictureSizes();
        List<Camera.Size> sampleRatios = new ArrayList<>();
        List<Pair<Float, Camera.Size>> otherRatios = new ArrayList<>();
        for (int i = 0; i < ss.size(); i++)
        {
            //获取到的size width > height
            Camera.Size size = ss.get(i);
            L.e(i + " pic, width = " + size.width + " height = " + size.height);
            if (previewHeight == size.height && previewWidth == size.width)
            {
                return size;
            }
            float cr = simple2(size.width * 1.0f / size.height);
            if (cr == pRatio)
            {
                sampleRatios.add(size);
            } else
            {
                otherRatios.add(new Pair<>(cr, size));
            }
        }
        if (sampleRatios.size() > 0)
        {
            List<Pair<Integer, Integer>> map = new ArrayList<>();
            for (int i = 0; i < sampleRatios.size(); i++)
            {
                Camera.Size size = sampleRatios.get(i);
                int delta = size.height - previewHeight;
                if (delta >= 0)
                {
                    map.add(new Pair<>(i, delta));
                }
            }
            Collections.sort(map, new Comparator<Pair<Integer, Integer>>()
            {
                @Override
                public int compare(Pair<Integer, Integer> o1, Pair<Integer, Integer> o2)
                {
                    int r1 = o1.second;
                    int r2 = o2.second;
                    return Integer.compare(r1, r2);
                }
            });
            Pair<Integer, Integer> first = map.get(0);
            return sampleRatios.get(first.first);
        }
        Collections.sort(otherRatios, new Comparator<Pair<Float, Camera.Size>>()
        {
            @Override
            public int compare(Pair<Float, Camera.Size> o1, Pair<Float, Camera.Size> o2)
            {
                float r1 = o1.first;
                float r2 = o2.first;
                float ret = Math.abs(r1 - pRatio) - Math.abs(r2 - pRatio);
                return ret > 0 ? 1 : ret < 0 ? -1 : 0;
            }
        });
        return otherRatios.get(0).second;
    }

    private static float simple2(float number)
    {
        BigDecimal bd = new BigDecimal(String.valueOf(number));
        //未保留小数的舍弃规则，RoundingMode.FLOOR表示直接舍弃。
        bd = bd.setScale(2, RoundingMode.FLOOR);
        return bd.floatValue();
    }

    public static void adjustPreviewOrientation(Activity activity, int cameraId, Camera camera)
    {
        if (camera == null) return;
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        adjustPreviewOrientation(rotation, cameraId, camera);
    }

    public static void adjustPreviewOrientation(int screenRotation, int cameraId, Camera camera)
    {
        if (camera == null) return;

        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        L.e("camera current orientation = " + info.orientation + ", screen orientation = " + screenRotation);
        int degrees = 0;
        switch (screenRotation)
        {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }
        int result = 0;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
        {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;
        } else
        {
            result = (info.orientation - degrees + 360) % 360;
        }
        //设置角度
        camera.setDisplayOrientation(result);
    }

    public static void takePhoto(Camera camera, final int id, final int screenRotation, final String path,
                                 final PictureCallback callback, final ExecutorService...service)
    {
        camera.takePicture(null, null, new Camera.PictureCallback()
        {
            @Override
            public void onPictureTaken(final byte[] data, Camera camera)
            {
                camera.startPreview();
                Runnable task = new Runnable()
                {
                    @Override
                    public void run()
                    {
                        try
                        {
                            Bitmap raw = BitmapFactory.decodeByteArray(data, 0, data.length);
                            Bitmap target = adjustPictureAngle(raw, id, screenRotation);
                            File f = new File(path);
                            if(!f.getParentFile().exists()){
                                f.getParentFile().mkdirs();
                            }
                            FileOutputStream fos = new FileOutputStream(f);
                            target.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                            fos.close();
                            callback.onTakePicture(path);
                            raw.recycle();
                            target.recycle();
                        }catch (Exception e){
                            callback.onTakePicture(null);
                        }
                    }
                };
                if(service != null && service.length > 0){
                    service[0].submit(task);
                }else{
                    new Thread(task).start();
                }
            }
        });
    }

    private static Bitmap adjustPictureAngle(Bitmap bmp, int cid, int screenRotation)
    {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cid, info);
        //这里是传感器逆时针旋转的角度，所以顺时针转过来就可以了，要注意前置摄像头是水平镜像的
        int sensorAngle = info.orientation;
        int degrees = 0;
        switch (screenRotation)
        {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }
        int result = 0;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
        {
            result = (sensorAngle + degrees) % 360;
            result = (360 - result) % 360;
        } else
        {
            result = (sensorAngle - degrees + 360) % 360;
        }
        Matrix mx = new Matrix();
        // T x M
        mx.postRotate(result);
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
        {
            mx.postScale(-1f, 1f);
        }
        return Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), mx, true);
    }

    public interface PictureCallback{
        void onTakePicture(String path);
    }

    public interface FrameCallback{
        void onCameraFrame(byte[] raw, int previewWidth, int previewHeight, int format);
    }

    public interface TextureAttachCallback{
        void onAvailable(SurfaceTexture surface, int previewWid, int previewHei, int textureWid, int textureHeight);
    }
}
